package com.wlyuan.open.application.service;

import com.alibaba.fastjson.JSON;
import com.wlyuan.core.domain.event.DomainEventPublisher;
import com.wlyuan.core.domain.valueobj.TenantId;
import com.wlyuan.core.domain.valueobj.UserId;
import com.wlyuan.open.OpenBaseSyncDTO;
import com.wlyuan.open.api.IOpenSyncService;
import com.wlyuan.open.domain.source.*;
import com.wlyuan.open.domain.source.assembler.AssemblerConvert;
import com.wlyuan.open.domain.source.dispatcher.OpenSyncDispatcher;
import com.wlyuan.open.domain.source.event.SourceReceived;
import com.wlyuan.open.domain.source.event.cache.OpenCacheRelation;
import com.wlyuan.open.domain.source.repository.SourceRepository;
import com.wlyuan.open.domain.source.repository.cache.OpenTenantCacheRepository;
import com.wlyuan.open.dto.OpenSearchSourceDataDTO;
import com.wlyuan.open.dto.OpenTenantSyncDTO;
import com.wlyuan.open.dto.OpenUserSyncDTO;
import com.wlyuan.open.utils.LongUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.apache.dubbo.config.annotation.DubboService;

import java.util.List;
import java.util.Optional;

/**
 * @author yuanzheng
 */
@RequiredArgsConstructor
@Slf4j
@DubboService
public class OpenSyncServiceImpl implements IOpenSyncService {
    private final SourceRepository sourceRepository;
    private final OpenSyncDispatcher openSyncDispatcher;
    private final OpenTenantCacheRepository openTenantCacheRepository;
    private final AssemblerConvert assemblerConvert;

    private <T extends OpenBaseSyncDTO> SourceData createRaw(T source) {
        SourceData raw = new SourceData();
        raw.setAppId(source.getAppId());
        raw.setStatus(SourceStatus.READY);
        raw.setSourceKey(source.getSourceKey());
        raw.setSourceRaw(JSON.toJSONString(source));
        raw.setSourceReq(assemblerConvert.convert(source));
        raw.setSourceClass(SourceClass.fromClass(source));
        raw.setCreateBy(new UserId(source.getUserId()));
        raw.setTenantId(new TenantId(source.getTenantId()));
        sourceRepository.save(raw);
        return raw;
    }

    private <T extends OpenBaseSyncDTO> void createRelation(T source, Long targetId) {
        SourceRelation relation = new SourceRelation();
        relation.setTargetId(targetId);
        relation.setSourceKey(source.getSourceKey());
        relation.setSourceClass(SourceClass.fromClass(source));
        //同步用户时会手动设置为空，入关系时不能为空，手动设置为0
        relation.setCreateBy(new UserId(Optional.ofNullable(source.getUserId()).orElse(0L)));
        //新增租户信息同步时，tenantId到下游服务时手动设置为空，入关系时不能为空
        if (source instanceof OpenTenantSyncDTO) {
            var companyTenant = (OpenTenantSyncDTO) source;
            relation.setTenantId(new TenantId(Long.valueOf(companyTenant.getSourceKey())));
        } else {
            relation.setTenantId(new TenantId(source.getTenantId()));
        }
        sourceRepository.save(relation);
    }

    @Override
    public <T extends OpenBaseSyncDTO> void sync(T source) {
        // 保存同步源数据
        var raw = createRaw(source);
        // 同步之后保存关系时，租户id已转换成新系统租户id，所以这里查询关系是否存在时，租户id也需要转换成新的租户id
        var pattern = new SourcePattern(source.getSourceKey(),
                SourceClass.fromClass(source), new TenantId(changeTenantId(source, source.getTenantId())));
        var relation = sourceRepository.ofPattern(pattern);
        // 源数据关系不存在
        if (null == relation) {
            //同步
            var targetId = openSyncDispatcher.sync(source);
            // targetId 必须存在
            Optional.ofNullable(targetId).ifPresent(t -> {
                if (LongUtils.notNullLong(t)) {
                    createRelation(source, t);
                    //关系id缓存
                    DomainEventPublisher.publish(new OpenCacheRelation(source.getSourceKey(), source, new SourceCache(t, "")));
                }
            });
        } else {
            // 发生监听事件，重写数据
            DomainEventPublisher.publish(new SourceReceived<>(raw, relation, source));
        }
    }

    private <T extends OpenBaseSyncDTO> Long changeTenantId(T source, Long sourceTenantId) {
        if (source instanceof OpenTenantSyncDTO) {
            return sourceTenantId;
        }
        var tenantId = openTenantCacheRepository.convert(sourceTenantId);
        return Optional.ofNullable(tenantId).orElse(sourceTenantId);
    }

    private <T extends OpenBaseSyncDTO> void sourceProcess(T source) {
        //同步用户时
        if (source instanceof OpenUserSyncDTO) {
            source.setUserId(null);
        }
        //新增租户信息，同步时，tenantId 传到下游服务时为空
        if (source instanceof OpenTenantSyncDTO) {
            source.setTenantId(null);
        }
    }

    @Override
    public void sourceDataSuccess(String sourceKey) {
        var pattern = new SourcePattern(sourceKey, null, null);
        var sourceDataId = sourceRepository.ofSourcePattern(pattern);
        sourceRepository.updateSource(sourceDataId, SourceStatus.SUCCEED.getValue(), SourceStatus.SUCCEED.getName());
    }

    @Override
    public void sourceDataFail(String sourceKey, String remark) {
        var pattern = new SourcePattern(sourceKey, null, null);
        var sourceDataId = sourceRepository.ofSourcePattern(pattern);
        sourceRepository.updateSource(sourceDataId, SourceStatus.FAILED.getValue(), remark);
    }

    @Override
    public List<String> getFailSourceData(OpenSearchSourceDataDTO search) {
        var pattern = new SourceDataPattern(SourceClass.fromValue(search.getSourceClass()), search.getTenantId(), search.getCreateAt());
        return sourceRepository.ofFailPattern(pattern);
    }

}
